package com.jjiang.bbmall.seckill.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.jjiang.bbmall.seckill.feign.CouponFeignService;
import com.jjiang.bbmall.seckill.feign.ProductFeignService;
import com.jjiang.bbmall.seckill.interceptor.LoginUserInterceptor;
import com.jjiang.bbmall.seckill.service.SeckillService;
import com.jjiang.bbmall.seckill.to.SeckillSkuRedisTo;
import com.jjiang.bbmall.seckill.vo.SeckillSessionsWithSkus;
import com.jjiang.common.to.SeckillOrderTo;
import com.jjiang.common.vo.SeckillSkuRelationVo;
import com.jjiang.common.vo.SkuInfoVo;
import com.jjiang.common.constant.SecKillConstant;
import com.jjiang.common.utils.R;
import com.jjiang.common.vo.MemberEntityVo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public void upLoadSeckillSku() {
        // 1.扫描需要参与秒杀的活动
        R r = couponFeignService.getLatestSessionByDays(3);
        if (r.getCode() == 0) {
            // 上架商品
            List<SeckillSessionsWithSkus> sessionData = r.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {
            });
            // 缓存到Redis
            // 1. 缓存活动信息
            this.saveSessionInfos(sessionData);
            // 2. 缓存活动关联的商品信息
            this.saveSessionSkuInfos(sessionData);
        }
    }

    public List<SeckillSkuRedisTo> getCurrentSecKillSkusBlockHandler(BlockException e) {
        System.out.println("getCurrentSecKillSkusBlockHandler被限流了！");
        return null;
    }

    @SentinelResource(value = "getCurrentSecKillSkus", blockHandler = "getCurrentSecKillSkusBlockHandler")
    @Override
    public List<SeckillSkuRedisTo> getCurrentSecKillSkus() {
        // 1.确定当前时间有哪些秒杀场次
        long now = new Date().getTime();
        Set<String> keys = redisTemplate.keys(SecKillConstant.SECKILL_SESSIONS_CACHE_PREFIX + "*");
        if (keys != null && !keys.isEmpty()) {
            for (String key : keys) {
                String keyTime = key.replace(SecKillConstant.SECKILL_SESSIONS_CACHE_PREFIX, "");
                String[] time = keyTime.split("_");
                long startTime = Long.parseLong(time[0]);
                long endTime = Long.parseLong(time[1]);
                if (startTime <= now && now <= endTime) {
                    // 当前的场次信息
                    // 2.获取所有秒杀的商品
                    List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                    if (range == null || range.isEmpty()) {
                        continue;
                    }
                    // 去商品秒杀redis库中去查
                    BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SecKillConstant.SECKILL_SKU_CACHE_PREFIX);
                    List<String> objects = hashOps.multiGet(range);
                    if (objects == null || objects.isEmpty()) {
                        continue;
                    }
                    // 查询得到
                    return objects.stream().map(object -> JSON.parseObject(object, SeckillSkuRedisTo.class)).collect(Collectors.toList());
                }
            }
        }
        return null;
    }

    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        // 1.找到所有需要参与秒杀的商品
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SecKillConstant.SECKILL_SKU_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        if (keys == null || keys.isEmpty()) {
            return null;
        }
        String reg = "\\d+_" + skuId;
        for (String key : keys) {
            // 进行匹配
            boolean matches = Pattern.matches(reg, key);
            if (matches) {
                // 找到了场次
                String json = hashOps.get(key);
                SeckillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
                Long startTime = secKillSkuRedisTo.getStartTime();
                Long endTime = secKillSkuRedisTo.getEndTime();
                long now = new Date().getTime();
                if (startTime <= now && now <= endTime) {
                    // 秒杀中
                } else if (startTime > now) {
                    // 是秒杀预告，不能携带随机码
                    secKillSkuRedisTo.setRandomCode(null);
                } else {
                    continue;
                }
                return secKillSkuRedisTo;
            }
        }
        return null;
    }

    @Override
    public String kill(String killId, String key, Integer num) {
        //1.获取当前商品的详细信息
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SecKillConstant.SECKILL_SKU_CACHE_PREFIX);
        String s = hashOps.get(killId);
        if (StringUtils.isEmpty(s)) {
            //没有此商品
            return null;
        }
        SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(s, SeckillSkuRedisTo.class);
        //2.校验合法性
        //2.1校验时间合法性
        Long startTime = seckillSkuRedisTo.getStartTime();
        Long endTime = seckillSkuRedisTo.getEndTime();
        long now = new Date().getTime();
        if (!(startTime <= now && now <= endTime)) {
            // 不在秒杀的时间内
            return null;
        }
        //2.2校验随机码和killId
        String randomCode = seckillSkuRedisTo.getRandomCode();
        String ourKillId = seckillSkuRedisTo.getSkuRelation().getPromotionSessionId() + "_" + seckillSkuRedisTo.getSkuInfo().getSkuId();
        if (!Objects.equals(key, randomCode) || !ourKillId.equals(killId)) {
            return null;
        }
        //3.验证购物数量是否正确
        int limit = seckillSkuRedisTo.getSkuRelation().getSeckillLimit().intValue();
        if (num > limit) {
            return null;
        }
        //4.验证该人是否已经秒杀过。即幂等性处理
        MemberEntityVo memberEntityVo = LoginUserInterceptor.loginUser.get();
        String existId = SecKillConstant.SECKILL_MEMBER_PREFIX + memberEntityVo.getId() + "_" + killId;
        //需要设置自动过期时间
        long ttl = endTime - startTime;
        Boolean notExist = redisTemplate.opsForValue().setIfAbsent(existId, num.toString(), ttl, TimeUnit.MILLISECONDS);
        if (Boolean.FALSE.equals(notExist)) {
            return null;
        }
        //5.使用分布式信号量来进行秒杀
        RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SECKILL_SKU_STOCK_SEMAPHORE + key);
        boolean b = semaphore.tryAcquire(num);
        if (!b) {
            //秒杀失败
            return null;
        }
        //秒杀成功,快速下单
        String orderSn = IdWorker.getTimeId();
        //发送MQ消息
        SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
        seckillOrderTo.setOrderSn(orderSn);
        seckillOrderTo.setMemberId(memberEntityVo.getId());
        seckillOrderTo.setNum(num);
        seckillOrderTo.setSkuRelationVo(seckillSkuRedisTo.getSkuRelation());
        seckillOrderTo.setSkuInfoVo(seckillSkuRedisTo.getSkuInfo());

        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTo);
        return orderSn;
    }

    private void saveSessionSkuInfos(List<SeckillSessionsWithSkus> sessionData) {
        sessionData.forEach(session -> {
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();
            String key = SecKillConstant.SECKILL_SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
            if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
                // 如果已经有了，则无需做重复缓存
                // 没有的时候，才需要缓存
                List<String> skuIds = session.getRelationSkus().stream().map(seckillSkuRelationVo -> seckillSkuRelationVo.getPromotionSessionId().toString() + "_" + seckillSkuRelationVo.getSkuId().toString()).collect(Collectors.toList());
                // 缓存活动信息
                redisTemplate.opsForList().leftPushAll(key, skuIds);
            }
        });
    }

    private void saveSessionInfos(List<SeckillSessionsWithSkus> sessionData) {
        // 准备hash操作
        String key = SecKillConstant.SECKILL_SKU_CACHE_PREFIX;
        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(key);
        sessionData.forEach(session -> {
            List<SeckillSkuRelationVo> relationSkus = session.getRelationSkus();
            relationSkus.forEach(seckillSkuRelationVo -> {
                // 缓存商品
                Long skuId = seckillSkuRelationVo.getSkuId();
                // 随机码【防止脚本进行抢秒杀】
                String randomCode = UUID.randomUUID().toString().replace("-", "");
                String putKey = seckillSkuRelationVo.getPromotionSessionId() + "_" + skuId.toString();
                if (Boolean.FALSE.equals(ops.hasKey(putKey))) {
                    // 没有时才进行缓存
                    SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                    // 1.sku基本数据
                    R r = productFeignService.info(skuId);
                    if (r.getCode() == 0) {
                        SkuInfoVo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                        });
                        redisTo.setSkuInfo(skuInfo);
                    }

                    // 2.sku的秒杀信息
                    redisTo.setSkuRelation(seckillSkuRelationVo);

                    // 3.设置开始结束时间
                    redisTo.setStartTime(session.getStartTime().getTime());
                    redisTo.setEndTime(session.getEndTime().getTime());

                    // 4.随机码【防止脚本进行抢秒杀】
                    redisTo.setRandomCode(randomCode);

                    // 保存数据
                    String redisToString = JSON.toJSONString(redisTo);
                    ops.put(putKey, redisToString);

                    String semaphoreKey = SecKillConstant.SECKILL_SKU_STOCK_SEMAPHORE + randomCode;
                    // 不存在的时候才设置
                    RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
                    // 5.商品可以秒杀的数量作为信号量
                    semaphore.trySetPermits(seckillSkuRelationVo.getSeckillCount().intValue());
                }
            });

        });
    }
}
